# Copyright (c) 2014, Neville-Neil Consulting
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# Neither the name of Neville-Neil Consulting nor the names of its 
# contributors may be used to endorse or promote products derived from 
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# Author: George V. Neville-Neil
#
# Description: A set of libraries to process NTP peer and loopstat
# files.  Used by various command line tools in this directory.
#

require(zoo) # Time series objects

# Functions for working with NTP log output

#' Read an NTPd loop file
#' 
#' @param file A properly formatted NTP loop file generated by the daemon
#'
#' @return The raw data frame, merged timeseries (offset, delay, m->s, s->)
#'          offset time series, delay time series,
#'          master.to.slave time series, and slave.to.master time series
#'
#' @export
ntpLoopRead <- function(file) {

    # Attempt to read log files from version 2.3 first.
    data = read.table(file, fill=TRUE, sep=" ",
        col.names=c("day", "second", "offset", "drift", "error", "stability", "interval"),
        blank.lines.skip=TRUE, header=FALSE, skip=100)

    # Remove any duplicates
    data = data[!duplicated(data$second), ]

    offset = zoo(data$offset, data$second)
    drift = zoo(data$drift, data$second)
    stability = zoo(data$stability, data$second)
    ts = merge(offset, drift, stability)

    return (list(data=data, ts=ts, offset=offset, drift=drift,
                 stability=stability))
}

#' Read an NTPd peer file
#' 
#' @param file A properly formatted NTP peer file generated by the daemon
#'
#' @return The raw data frame, merged timeseries (offset, drift, error)
#'
#' @export
ntpPeerRead <- function(file) {

    # Attempt to read log files from version 2.3 first.
    data = read.table(file, fill=TRUE, sep=" ",
        col.names=c("day", "second", "address", "status", "offset", "delay", "dispersion", "skew"), colClasses=c("NULL", NA, NA, NA, NA, NA, NA, NA),
        blank.lines.skip=TRUE, header=FALSE, skip=100)

    sources <- list()
    addrs = unique(data$address)
    for (src in addrs) {
        sources[[toString(src)]] <- data[data$address == src,]
    }

    cat("Returning", length(sources), "sources, index as any of: ")
    for (src in addrs) {
        cat(toString(src), " ")
    }
    cat("\n")
    return (sources)

}

#' Graphs a dataframe returned by a call to ntpPeer/LogRead
#'
#' @param logframe A data frame returned by ptpLogRead
#' @param value Graph only a specific value rather than the four major ones.
#' @param output An optional file name to place a PNG of the graph into
#'
#' @export
ntpGraph <- function(logframe, value="offset", start, end, output) {

  if (!missing(output))
    png(filename=output, height=960, width=1280, bg="white")

  if (!missing(start))
    logframe = logframe[index(logframe) > as.POSIXct(start)]
  if (!missing(end))
    logframe = logframe[index(logframe) < as.POSIXct(end)]
  if (!missing(start) && !missing(end))
    logframe = logframe[index(logframe) > as.POSIXct(start) &
      index(logframe) < as.POSIXct(end)]

  plot(logframe[[value]], type="p", cex=.1, main="NTP Log Graph", xlab="Time", mar=c(1, 5.1, 1, 5.1))
  if (!missing(output))
      dev.off()
  return(NULL)

}
    
ntpGraphAll <- function(frames, value="offset") {

    plot(frames[[1]]["offset"], type="p", cex=.1, main="NTP Log Graph", xlab="Time", mar=c(1, 5.1, 1, 5.1))

}


#' Draw a simple histogram of some one variable in our log
#'
#' @param dataframe returned by ntpLoopRead()
#' @param start start time in strftime format
#' @param end end time in strftime format
#' @param output optional file in which to store the graph
#'
#' @export
ntpHistogram <- function(log, start, end, output) {
    if (!missing(output))
        png(filename=output, height=960, width=1280, bg="white")
    if (!missing(start))
        log = log[index(log) > as.POSIXct(start)]
    if (!missing(end))
        log = log[index(log) < as.POSIXct(end)]
    if (!missing(start) && !missing(end))
        log = log[index(log) > as.POSIXct(start) &
            index(log) < as.POSIXct(end)]
    left = min(log)
    right = max(log)
    height = max(length(log)) * 0.69
    hist (log, xlim=c(left, right), ylim=c(0,height), breaks=20,
          col=rgb(1.0, 0, 0, 0.5))
    if (!missing(output))
        dev.off()
}

#' Textual output of the loop or peer file's statistics
#'
#' @param dataframe returned by ntpLoopRead()
#' @param value the value to look at (offset, skew, etc.)
#' @param boundary measure past which we are out of SLA in ns
#'
#' @export
ntpStats <- function(log, value = "offset", boundary = 1000) {

    if (!(value %in% colnames(log))) {
        cat("Invalid value, choose one of:", colnames(log))
        return (NULL)
    }
   
    cat("Measurements: ", length(log[value]))
    cat("\nOffset",
        "\nmin:", min(log[[value]], na.rm=TRUE),
        " max: ", max(log[[value]], na.rm=TRUE),
        " median: ", median(log[[value]], na.rm=TRUE),
        " mean: ", mean(log[[value]], na.rm=TRUE),
        "\nstd dev: ", sd(log[[value]], na.rm=TRUE),
        " variance: ", var(log[[value]], na.rm=TRUE), "\n")
    cat("\nOutside Boundary: ", sum(log[[value]] > boundary) + sum(log[[value]] < -boundary),
        "Percentage: ", (sum(log[[value]] > boundary) + sum(log[[value]] < -boundary)) / length(log[[value]]), "\n")
}

